var Topics = {
  topics: {},
  publishers: {},  
  mixin: function() {
    for (var i=0; i < arguments.length; i++) {
      Object.extend(arguments[i], this.methods);
    };
  },
  _notify: function(methodName) {
    var topics = this.publishers[methodName].topics;
    for (var i = 0; i < topics.length; i++) {
      var topic = this.topics[topics[i]];
      if (topic) {
        for (var j = 0; j < topic.length; j++) {
          topic[j]();
        }
      }
    }
  },
  methods: {
    publish: function(topic, methodName) {
      var method = this[methodName];
      if (!Topics.publishers[methodName]) {
        Topics.publishers[methodName] = {original: method, topics: []};
        this[methodName] = function() {      
          method.apply(this, arguments);
          Topics._notify(methodName);
        };
      }
      Topics.publishers[methodName].topics.push(topic);
    },    
    unpublish: function(topic, methodName) {
      if (Topics.publishers[methodName]) {
        this[methodName] = Topics.publishers[methodName].original;
        delete Topics.publishers[methodName];
      }
    },
    subscribe: function(topic, methodName) {
      if (!Topics.topics[topic]) Topics.topics[topic] = [];
      Topics.topics[topic].push(this[methodName]);
    },
    unsubscribe: function(topic, methodName) {
      if (Topics.topics[topic]) {
        var newList = [];
        for (var i = 0; i < Topics.topics[topic].length; i++) {
          if (this[methodName] != Topics.topics[topic][i]) newList.push(Topics.topics[topic][i]);
        }
        delete Topics.topics[topic];
        if (newList.length > 0) Topics.topics[topic] = newList;
      }    
    }
  }
};